﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using FishServe.Core.DesignByContract;
using FishServe.Core.Services.UpdaterService.Common;
using FishServe.Core.Services.UpdaterService.Interfaces;

namespace FishServe.Core.Services.UpdaterService
{
	public sealed class ReleaseManifestValidator : IReleaseManifestValidator
	{
		#region Delegates - Mapper
		delegate FileCheckTask ManifestModuleMap(ManifestModuleInfo pSource, string baseDirectory);
		delegate FileCheckTask IncludedFileMap(IncludeFile pSource, string baseDirectory);
		#endregion
		private List<FileCheckTask> mFileCheckTasks = null;

		internal const string MODULE_DIR = @"{0}\Modules";

		public bool ValidateReleaseManifest(ReleaseManifest releaseManifest)
		{
			Console.WriteLine("Working on ReleaseManifest: {0}", releaseManifest.Filename);
			FileInfo fi = new FileInfo(releaseManifest.Filename);
			string baseDirectory = String.Format(MODULE_DIR, fi.Directory.FullName);
			HashAlgorithm hashAlgorithm = Extensions.GetHashAlgorithm(releaseManifest.FileHashAlgorithm);

			mFileCheckTasks = RetrieveFilesForChecks(releaseManifest, baseDirectory);
			CheckFileExists(ref mFileCheckTasks);
			CheckFileHash(ref mFileCheckTasks, hashAlgorithm);
			mFileCheckTasks.ForEach( p => Console.WriteLine(p) );

			Console.WriteLine("=============");

			List<FileCheckTask> val = FilesThatFailedChecks(mFileCheckTasks);
			Console.WriteLine("Count: {0}", val.Count);
			Console.WriteLine("Following Files Failed Checks");
			val.ForEach(p =>Console.WriteLine(p));

			return (val.Count == 0);
		}

		public IList<FileCheckTask> RetrieveFilesThatFailedChecks()
		{
			DBC.Assert(mFileCheckTasks != null, "FileCheckTasks not initialised.");
			return FilesThatFailedChecks(mFileCheckTasks);
		}


		private List<FileCheckTask> RetrieveFilesForChecks(ReleaseManifest releaseManifest, string baseDirectory)
		{
			List<FileCheckTask> retValue = new List<FileCheckTask>();
			List<ManifestModuleInfo> manifestInfos = new List<ManifestModuleInfo>();

			ManifestModuleMap oManifestModuleMap = new ManifestModuleMap(MapFromManifestModuleInfo);
			IncludedFileMap oIncludedFileMap = new IncludedFileMap(MapFromIncludedFile);

			foreach (ModuleManifest item in releaseManifest.ModuleManifests)
			{
				if (!item.ManifestType.Equals(ModuleManifestType.Content))
				{
					retValue.Add(oManifestModuleMap(item.Module, baseDirectory));
				}
				item.IncludeFiles.ForEach(p =>
				{
					retValue.Add(
						oIncludedFileMap(p, MyPath.Combine(baseDirectory, item.Module.Name, item.Module.Version.ToString()))
					);
				});

			}
			return retValue;
		}
		private void CheckFileExists(ref List<FileCheckTask> pFilesToCheck)
		{
			pFilesToCheck.ForEach(p =>
			{
				if (File.Exists(p.FullFilePath))
				{
					p.ResultState |= Result.FilePresent;
				}
				else
				{
					p.ResultState &= ~Result.FilePresent;
				}

			});
		}
		private void CheckFileHash(ref List<FileCheckTask> pFilesToCheck, HashAlgorithm pHashAlgorithm)
		{
			pFilesToCheck.ForEach(p =>
			{
				try
				{
					if (Extensions.ComputeFileHash(p.FullFilePath, pHashAlgorithm).Equals(p.FileHash))
					{
						p.ResultState |= Result.FileHashPassed;
					}
					else
					{
						p.ResultState &= ~Result.FileHashPassed;
					}
				}
				//Can NOT assume that the CheckFileExists method is called first before this.
				catch (System.IO.IOException) // DirectoryNotFound or FileNotFound Exception
				{
					p.ResultState &= ~Result.FileHashPassed;
				}
			});
		}

		private List<FileCheckTask> FilesThatFailedChecks(List<FileCheckTask> pSource)
		{
			return pSource.FindAll(p =>
			{
				return !p.ResultState.ConstainsResult(Result.FilePresent) | !p.ResultState.ConstainsResult(Result.FileHashPassed);
			});
		}

		#region Static Mapper Logic

		private static FileCheckTask MapFromIncludedFile(IncludeFile pSource, string baseDirectory)
		{
			return new FileCheckTask()
			{
				FullFilePath = Path.Combine(baseDirectory, pSource.FileName),
				FileHash = pSource.FileHash
			};
		}
		private static FileCheckTask MapFromManifestModuleInfo(ManifestModuleInfo info, string baseDirectory)
		{
			return new FileCheckTask()
					{
						FullFilePath = MyPath.Combine(baseDirectory, info.Name, info.Version.ToString(), String.Format("{0}.dll", info.Name)),
						FileHash = info.FileHash
					};

		}
		#endregion
	}
}
